/**
  ETFDAQ Project
  \class TAv1290A
  \brief 32 ch ECL input TDC VME module from CAEN Corp. of Italy
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2024-09-03
  \date 2024-09-04 last modified

  \copyright Copyright (c) 2024 IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <unistd.h>
#include <bitset>
#include <climits>
#include "TAv1290A.h"
#include "TAMsg.h"

using std::cout;
using std::endl;

#define er TAMsg::Error

// resolution for various res modes
const double TAv1290A::kBLIP[4] = {
  25./pow(2.,5.), 25./pow(2.,7.), 25./pow(2.,8.), 25./pow(2.,10.)
};

TAv1290A::TAv1290A(TAController *ctrl, const TAYaml &c) : TACAENCard(ctrl, c),
fNw{0}, fMatchWindow(0x14), fWindowOffset(0xFFD8), fRejectMargin(0x4),
fSearchMargin(0x8), fopd{0}, fRes(3), fBlip(kBLIP[3]){
  fCardPurpose = ECardPurpose::TDC;
  fMAX_EVCTR = UINT_MAX - 100;
} // end ctor

TAv1290A::~TAv1290A(){}

void TAv1290A::Initialize(){
  const time_t t0 = TAMsg::ms();
  Config(); // read the user yaml input
  SoftwareReset(); // SR, clear data and most of other config registers
  SlotInit(); // set slot and crate ids
  Identify(); // read card rom for manufacturer info
  // configure interrupt //
  if(fIRQLevel) ConfigIRQ(fIRQLevel, fIRQVector, fEventTrigger);
  cout << endl;
  TAMsg::Info("TAv1290A", "Initialize: communicating with the micro-controller..."
    "\nThis may take a while. Please be patient....");
  if(fChOn.size() == fNCh){
    std::bitset<64> v;
    for(size_t i = 0; i < fNCh; i++) v.set(i, fChOn[i]);
    SetChOnPattern(v.to_ulong() & 0xffffffff);
  } // end else
  else writeop(v1290op::EN_ALL_CH); // all chs on

  const auto &c = fConfig; // just for convenience
  // configure trigger
  SetTriggerMatchingMode();
  SetMatchWindow(fMatchWindow = c["matchWindow"].as<int>());
  SetWindowOffset(fWindowOffset = c["windowOffset"].as<int>());
  if(c["searchMargin"])
    SetSearchMargin(fSearchMargin = c["searchMargin"].as<int>());
  if(c["rejectMargin"])
    SetRejectMargin(fRejectMargin = c["rejectMargin"].as<int>());
  if(c["subtractTrigger"]){
    if(c["subtractTrigger"].as<bool>()) EnableTrigSubtraction();
    else EnableTrigSubtraction(false);
  } // end if
  // check the validity of the trig config //
  if(fWindowOffset + fMatchWindow < 0){
    if(abs(fWindowOffset) > 51200) // = 2048*25
      er("TAv1290A", "Initialize: trigger latency too large (<51.2us)");
  } // end if
  else if(fWindowOffset + fMatchWindow > 1000){ // = 40*25
    er("TAv1290A", "Initialize: match+triLatency too large (<1us)");
  } // end else

  SetDetection(3); // 0: pair; 1: only tr; 2: only le; 3: tr & le
  if(c["resolution"]) fRes = c["resolution"].as<uint16_t>();
  SetResolution(fRes); // 0: 800ps; 1: 200ps; 2: 100ps; 3: 25ps
  fBlip = kBLIP[fRes];
  EnableBerr(); // issue a berr on end of blt
  if(fIsEmptyEnable) EnableEmpty();
  EnableEventFIFO(); // necessary for knowing ev_len in ReadEvent
  // set max. Ne for each blt; default: 0, ev aligned blt disabled
  // DO NOT USE - or it will emits berr every bltev events
  // RegWriteA32D16(v1290reg::BLTEventNumber, 1);
  // just for fun for the time being -- usually the ECL outputs is not used
  SetOutProg(0x0); // 0: DRDY; 1: FULL; 2: ALM_FULL; 3: ERR

  cout << "\n\tIt took \033[36;1m" << (TAMsg::ms() - t0)/1000. << "s"
    "\033[0m for v1290 (slot " << fSlot << ") to initialize" << endl;
} // end member function Initialize

void TAv1290A::SlotInit(){
  RegWriteA32D16(v1290reg::GeoAddress, fSlot);
} // end member function SlotInit

void TAv1290A::Identify(){
  fFirmWareRev = RegReadA32D16(v1290reg::FirmwareRev);
  fSerialNo = RegReadA32D16(v1290rom::SerialNo1) << 8 |
    RegReadA32D16(v1290rom::SerialNo0);
  fHardWareRev =
    (RegReadA32D16(v1290rom::Revision3) & 0xFF) << 24 |
    (RegReadA32D16(v1290rom::Revision2) & 0xFF) << 16 |
    (RegReadA32D16(v1290rom::Revision1) & 0xFF) << 8 |
    (RegReadA32D16(v1290rom::Revision0) & 0xFF);
  fVersion = RegReadA32D16(v1290rom::Version);
} // end member function Identify

// level 1~7, 0 will disable the IRQ generation
// vector 0x00~0xFF, requested by vme ctrler to identify the interrupter
// nw: 16bits, trigger will be generated after nof buffered words reaches nw
void TAv1290A::ConfigIRQ(int level, int vector, int nw) const{
  RegWriteA32D16(v1290reg::InterruptLevel, level & 0x7); // 3 bits
  if(!level) return; // IRQ is disabled
  RegWriteA32D16(v1290reg::InterruptVector, vector & 0xFF); // 8 bits
  if(nw > 32735 || nw < 5)
    er("TAv1290A", "ConfigIRQ: illegal AlmostFullLevel: %d not in [5, 32735]", nw);
  RegWriteA32D16(v1290reg::AlmostFullLevel, nw & 0x7fff); // 14 bits
} // end member function ConfigIRQ

// to select to write the header and EOB (End of Block)
// anyway when there are no accepted channels
void TAv1290A::EnableEmpty() const{
  RegWriteA32D16(v1290reg::Control,
    RegReadA32D16(v1290reg::Control) | v1290mask::EMPTYEN);
} // end member function EnableEmpty

void TAv1290A::EnableBerr() const{
  RegWriteA32D16(v1290reg::Control,
    RegReadA32D16(v1290reg::Control) | v1290mask::BERR_EN);
} // end member function EnableBerr

// so that ev len is written in event fifo
void TAv1290A::EnableEventFIFO() const{
  RegWriteA32D16(v1290reg::Control,
    RegReadA32D16(v1290reg::Control) | v1290mask::EVENTFIFO);
} // end member function EnableEventFIFO

// true if buffered events are enough
bool TAv1290A::DataReady() const{
  // return RegReadA32D16(v1290reg::Status) & v1290mask::DRDY;
  return RegReadA32D16(v1290reg::EventFIFOStatus) & v1290mask::FIFODRDY;
} // end member function DataReady

int TAv1290A::ReadData(uint32_t *buf){
  ReadEventCounter(); // read the event counter
  int len = 0;
  for(int i = 0; i < fDNEv; i++){
    fNw[i] = RegReadA32D32(v1290reg::EventFIFO) & 0xffff; // length of event i
    len += fNw[i];
  } // end for over buffered events
  const int nget = RegReadA32BLT32(v1290reg::Buffer, buf, len);
  if(fDebug) PrintEvent(buf);
  return nget;
} // end member function ReadData

// \retval nof words read
int TAv1290A::ReadEvent(uint32_t *buf){
  er("TAv1290A", "ReadEvent: undefined, implemented in member function ReadData");
  return -1;
} // end member function ReadEvent

void TAv1290A::ClearData(){
  RegWriteA32D16(v1290reg::SoftwareClear, 0x0);
  if(fEventCounter > fMAX_EVCTR) ClearEventCounter();
} // end member function ClearData

void TAv1290A::ResetForNewRun(){
  ClearEventCounter();
} // end member function ResetForNewRun

void TAv1290A::ClearEventCounter(){
  RegWriteA32D16(v1290reg::SoftEventReset, 0x0);
} // end member function ClearEventCounter

void TAv1290A::SoftwareReset(){
  RegWriteA32D16(v1290reg::ModuleReset, 0x0);
} // end member function SoftwareReset

// return the event counter register
void TAv1290A::ReadEventCounter(){
  static unsigned evcnt0;
  if(fDebug) evcnt0 = fEventCounter;
  fEventCounter = RegReadA32D32(v1290reg::EventCounter);
  fDNEv = RegReadA32D16(v1290reg::EventFIFOStored) & 0x7ff;
  if(fDebug){
    cout << "\033[1m----- Event Counter Print -----\033[0m" << endl;
    cout << "total: " << fEventCounter;
    cout << ", counter difference: " << fEventCounter - evcnt0;
    cout << ", EventFIFO stored: " << fDNEv;
    cout << ", Event stored: " << RegReadA32D16(v1290reg::EventStored);
    cout << "\n\033[1m----- BYE -----\033[0m" << endl;
  } // end if
} // end member function ReadEventCounter


// read ok
bool TAv1290A::ro() const{
  time_t t0 = TAMsg::ms();
  unsigned i = 0;
  const int nt = 10000;
  while(1){
    if(RegReadA32D16(v1290reg::MicroHandshake) & v1290mask::READ_OK){
      // printf("TAv1290A, ro: waited %ldms and %u times for read ok\r",
      //   TAMsg::ms() - t0, i); fflush(stdout);
      return true;
    } // end if
    if(i++ > nt){
      TAMsg::Warn("TAv1290A", "ro: test read_ok for too long (%ldms, %u times)"
        " and abort\r", TAMsg::ms() - t0, i);
      return false;
    } // end if
    usleep(1);
  } // end while
  return false; // never reach here
} // end member function ro

// write ok
bool TAv1290A::wo() const{
  time_t t0 = TAMsg::ms();
  unsigned i = 0;
  const int nt = 1000000;
  while(1){
    if(RegReadA32D16(v1290reg::MicroHandshake) & v1290mask::WRITE_OK){
      // printf("TAv1290A, wo: waited %ldms and %u times for write ok\r",
      //   TAMsg::ms() - t0, i); fflush(stdout);
      return true;
    } // end if
    if(i++ > nt){
      TAMsg::Warn("TAv1290A", "wo: waited write_ok for too long (%ldms, %u times)"
        " and abort", TAMsg::ms() - t0, i);
      return false;
    } // end if
    usleep(1);
  } // end while
  return false; // never reach here
} // end member function wo

// op: opcode: COMMAND(8bit)|OBJECT(8bit)
// n: nd data
bool TAv1290A::readop(uint16_t op, uint16_t *data, int nd) const{
  if(wo()) RegWriteA32D16(v1290reg::Micro, op); // send the opcode
  else return false;

  for(int i = 0; i < nd; i++){ // read the data
    if(ro()) data[i] = RegReadA32D16(v1290reg::Micro);
    else return false;
  } // end if
  return true;
} // end member function readop

// op: opcode: COMMAND(8bit)|OBJECT(8bit)
// n: nd data
bool TAv1290A::writeop(uint16_t op, const uint16_t *data, int nd) const{
  if(wo()) RegWriteA32D16(v1290reg::Micro, op); // send the opcode
  else return false;

  for(int i = 0; i < nd; i++){ // write the data
    if(wo()) RegWriteA32D16(v1290reg::Micro, data[i]);
    else return false;
  } // end if
  return true;
} // end member function writeop

bool TAv1290A::writeop(uint16_t op) const{
  if(wo()) RegWriteA32D16(v1290reg::Micro, op); // send the opcode
  else return false;

  return true;
} // end member function writeop

// 32 bits corresponds to ch 0-31 respectively. 1: enable; 0:disable
void TAv1290A::SetChOnPattern(uint32_t pattern){
  const uint16_t p[2] = {
    static_cast<uint16_t>(pattern & 0xffff),
    static_cast<uint16_t>(pattern>>16 & 0xffff),
  };
  writeop(v1290op::WRITE_EN_PATTERN, p, 2);
} // end member function SetChOnPattern

void TAv1290A::SetTriggerMatchingMode() const{
  writeop(v1290op::TRIG_MATCH, nullptr, 0);
} // end memeber function SetTriggerMatchingMode

void TAv1290A::SetMatchWindow(int width) const{
  if(width < 0 || width > 51200) er("TAv1290A", "SetMatchWindow:"
      "can only in the range of (0, 51200), width: %d", width);
  const uint16_t w = uint16_t(width / CLK);
  writeop(v1290op::SET_WIN_WIDTH, &w);
} // end member function SetMatchWindow

void TAv1290A::SetWindowOffset(int offset) const{
  if(offset < -51200 || offset > 1000)
    er("TAv1290A", "SetWindowOffset:"
      "can only in the range of (-51200, +1000), offset: %d", offset);
  const uint16_t ofst = uint16_t(offset / CLK);
  writeop(v1290op::SET_WIN_OFFS, &ofst);
} // end member function SetWindowOffset

void TAv1290A::SetSearchMargin(int ex) const{
  if(ex < 0 || ex > 500) er("TAv1290A", "SetSearchMargin:"
      "can only in the range of (0, 500), margin: %d", ex);
  const uint16_t w = uint16_t(ex / CLK);
  writeop(v1290op::SET_SW_MARGIN, &w);
} // end member function SetSearchMargin

void TAv1290A::SetRejectMargin(int rm) const{
  if(rm < 0 || rm > 300) er("TAv1290A", "SetSearchMargin:"
      "can only in the range of (0, 300), margin: %d", rm);
  const uint16_t r = uint16_t(rm / CLK);
  writeop(v1290op::SET_REJ_MARGIN, &r);
} // end member function SetRejectMargin

void TAv1290A::EnableTrigSubtraction(bool opt) const{
  if(opt) writeop(v1290op::EN_SUB_TRIG);
  else writeop(v1290op::DIS_SUB_TRIG);
} // end member function EnableTrigSubtraction

// read and print trigger configuration
void TAv1290A::PrintTrigConfig() const{
  uint16_t d[5]; // match, offset, search margin, reject margin, trig_sub
  readop(v1290op::READ_TRIG_CONF, d, 5);

  cout << "----- v1290, slot " << fSlot << " Trigger Config -----" << endl;
  cout << "Match Window: " << d[0]*25;
  cout << "ns\nTrigger Latency (window offset): " << short(d[1])*25;
  cout << "ns\nSearch margin: " << d[2]*25;
  cout << "ns\nReject margin: " << d[3]*25 << "ns" << endl;
  cout << "Trigger subtraction: " << (d[4] & 0x1 ? "enabled" : "disabled") << endl;
  cout << "----- v1290 Trigger Config Print End -----" << endl;
} // end member function PrintTrigConfig

// 0: pair mode; 1: only trailing; 2: only leading; 3: trailing and leading
void TAv1290A::SetDetection(uint16_t mode) const{
  if(mode > 3) er("TAv1290A", "SetDetection: illegal mode: %u", mode);
  writeop(v1290op::SET_DETECTION, &mode);
} // end member function SetDetection

// 0: 800ps; 1: 200ps; 2: 100ps; 3: 25ps
void TAv1290A::SetResolution(uint16_t mode) const{
  if(mode < 0 || mode > 3)
    er("TAv1290A", "SetResolution: illegal resolution: %d, allowed: [0,3]", mode);
  writeop(v1290op::SET_RESOLUTION, &mode);
} // end member function SetResolution

// set the pout ECL output function
// 0: DRDY; 1: FULL; 2: ALM_FULL; 3: ERR
void TAv1290A::SetOutProg(uint16_t mode) const{
  if(mode > 3) er("TAv1290A", "SetOutProg: illegal mode: %u", mode);
  RegWriteA32D16(v1290reg::OutProg, mode);
} // end member function SetOutProg

void TAv1290A::Print() const{
  printf("\n\033[36;1m------ slot %d: %s ------\033[0m\n", fSlot, fCardName.data());
  printf("baseaddr: 0x%08x\n", fBaseAddress);
  printf("serialNo: %d, hardware version: %d\nfirmware version: %d, version: %d\n",
    fSerialNo, fHardWareRev, fFirmWareRev, fVersion);
  uint16_t microRev; readop(v1290op::READ_MICRO_REV, &microRev);
  printf("firmware version of the micro-controller: %u\n", microRev);

  cout << std::boolalpha;
  cout << "Empty Enable: " << fIsEmptyEnable << endl;
  cout << "IRQ Level: " << fIRQLevel << ",\t IRQ Vector: 0x";
  cout << std::hex << fIRQVector << std::dec;
  cout << ",\t Almost Full Level: " << RegReadA32D16(v1290reg::AlmostFullLevel);
  cout << " words" << endl;
  cout << std::noboolalpha;
  uint16_t en[2]{};
  readop(v1290op::READ_EN_PATTERN, en, 2);
  cout << "Channel On Pattern: " << endl;
  cout << "00-15: "; TAMsg::Print16(en[0]);
  cout << "16-31: "; TAMsg::Print16(en[1]);

  PrintTrigConfig();
  const int r[4] = {800, 200, 100, 25}; uint16_t res;
  readop(v1290op::READ_RES, &res);
  cout << "Resolution: \033[32;1m" << r[res& 0x3] << "ps\033[0m" << endl;
} // end member function Print

void TAv1290A::PrintEvent(const uint32_t *ev){
  cout << "\033[1m ___ " << fDNEv << " events in FIFO " << " ____ \033[0m" << endl;
  int pos = 0;
  for(int i = 0; i < fDNEv; i++){
    cout << "\033[32;1mevent idx " << i << ", Length ";
    cout << fNw[i] << " words\033[0m" << endl;
    for(int j = 0; j < fNw[i]; j++){
      cout << i << "-th " << " event, " << j << "-th " << " word" << endl;
      const int chData = ev[pos++];
      const int sig = chData>>27 & 0x1f, slot = chData & 0x1f;
      int chId = -1;
      bool isTrailing = false;
      double t = -999.;
      switch(sig){
        case 8: // global header
          cout << "v1x90 Global Header for v1x90, slot: " << slot << endl;
          cout << "Event count: " << (chData>>5 & 0x3FFFFF) << endl;
          break;
        case 16: // global trailer
          cout << "v1x90 Global Trailer for v1x90, slot: " << slot << endl;
          cout << "Word count: " << (chData>>5 & 0xFFFFF) << endl;
          break;
        case 1: // group header
          cout << "HPTDC group header for v1x90, slot: " << slot << endl;
          cout << "\tbunchId: " << (chData & 0xFFF);
          cout << "\teventId: " << (chData>>12 & 0xFFF) << endl;
          cout << "TDCId: " << (chData>>24 & 0x3) << endl;
          break;
        case 3: // group trailer
          cout << "HPTDC group trailer for v1x90, slot: " << slot << endl;
          cout << ((chData & 0xFFF) - 2) << " hits." << endl; // DEBUG
          cout << "wordCnt: " << (chData & 0xFFF);
          cout << "\teventId: " << (chData>>12 & 0xFFF) << endl;
          cout << "TDCId: " << (chData>>24 & 0x3) << endl;
          break;
        case 0: // data zone
          // if(ECardType::kV1190 == card) chId = chData>>19 & 0x7F;
          chId = chData>>21 & 0x1F;
          t = (chData & 0x7FFFF) * fBlip;
          isTrailing = chData>>26 & 0x1;
          cout << "chId: " << chId << ", " << (isTrailing ? "Trailing" : "Leading");
          cout << " edge" << ", time: " << t << endl;
          break;
        default: break; // should never reach here
      } // end switch
    } // end for over words in event i
  } // end for over events
} // end member function PrintEvent
